/**
 * Copyright 2007 Jason Horman, Pete Aykroyd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.functor;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.io.Serializable;

/**
 * A org.functor that takes no arguments and returns a value.
 */
public abstract class Function <R> implements Serializable {
	/**
	 * Evaluate this function.
	 *
	 * @return the result of this evaluation
	 */
	public abstract R evaluate();

	/**
	 * Constructs a Function that always returns the same value.
	 */
	public static Function constant(final Object value) {
		return new Constant(value);
	}

	/**
	 * Takes the result of this function and passes it to the UnaryPredicate.
	 */
	public Predicate to(final UnaryPredicate predicate) {
		return new Predicate() {
			public boolean test() {
				return predicate.test(evaluate());
			}
		};
	}

	/**
	 * Takes the result of this function and passes it to the UnaryProcedure.
	 */
	public Procedure to(final UnaryProcedure proc) {
		return new Procedure() {
			public void run() {
				proc.run(evaluate());
			}
		};
	}

	/**
	 * Takes the result of this function and passes it to the UnaryFunction.
	 */
	public Function to(final UnaryFunction function) {
		final Function me = this;
		return new Function() {
			public Object evaluate() {
				return function.evaluate(me.evaluate());
			}
		};
	}

	public Function toStringFunction() {
		final Function me = this;
		return new Function<String>() {
			public String evaluate() {
				return me.evaluate().toString();
			}

			public String toString() {
				return evaluate();
			}
		};
	}

	/**
	 * Returns a function that executes this function and the function passed in.
	 * Returns a 2 element collection containing the result of each of the
	 * functions.
	 */
	public Function and(final Function function) {
		final Function me = this;
		return new Function() {
			public Object evaluate() {
				return new Tuple(me.evaluate(), function.evaluate());
			}
		};
	}

	public static final Function NULL = constant(null);

	public static class Constant <ValueType> extends Function {
		private ValueType value;

		public Constant(ValueType value) {
			this.value = value;
		}

		public ValueType evaluate() {
			return value;
		}
	}

	public static class JavaFunction <ReturnType> extends Function {
		private Object self;
		private Method method;

		public JavaFunction(Method method) {
			this(null, method);
		}

		public JavaFunction(Object self, Method method) {
			this.self = self;
			this.method = method;
		}

		public ReturnType evaluate() {
			try {
				return (ReturnType) method.invoke(self);
			} catch (IllegalAccessException e) {
				throw new FunctorException(e);
			} catch (InvocationTargetException e) {
				throw new FunctorException(e);
			}
		}
	}
}
